package risoe.syslab.fhnew.controller;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.io.Serializable;
import java.net.URI;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;

import org.w3c.dom.Document;

import risoe.syslab.fhnew.common.SystemConstants.DeviceType;
import risoe.syslab.fhnew.gui.DocumentPreparedListener;
import risoe.syslab.fhnew.gui.DocumentUtils;
import risoe.syslab.fhnew.gui.VisualizationEvent;
import risoe.syslab.fhnew.gui.VisualizationEventListener;
import risoe.syslab.fhnew.hw.Actuator;
import risoe.syslab.fhnew.hw.Sensor;
import risoe.syslab.model.CompositeString;
import risoe.syslab.model.flexhouse.ControllerParameter;

/**
 * Class for adding controllers to the controllermanager with filtering and strict rules for controlling devicetypes
 * @author M. Svendsen
 */
public class ControllerConfig implements Serializable, DocumentPreparedListener, VisualizationEventListener{
	private static final long serialVersionUID = -1310624417059613925L;
	private String mName;
	private String mOwner;
	private PluginControllerInterface mPluginControllerInterface = null;

	private HashSet<String> mNameSubscriptions = new HashSet<String>();
	private EnumSet<DeviceType> mTypeSubscriptions = EnumSet.noneOf(DeviceType.class);
	private EnumSet<DeviceType> mControlledTypes = EnumSet.noneOf(DeviceType.class);
	private HashSet<ControllerParameter> mControllerParameters = new HashSet<ControllerParameter>();
	
	// Visualization related variables
	private ArrayList<String> mSubscribedSVGIds = new ArrayList<String>();
	private URI mSvgUri;
	private String mSerializedDocument;
	

	/**
	 * Creates a new controllerconfig. After creating a new controllerconfig, you must set the devicetypes to be controlled as
	 * well as the events to subscribe to.
	 * @param name the name of this config and its related controller
	 * @param owner the owner of this config and its related controller (name or initials)
	 * @param remote if this is a remote controller
	 */
	public ControllerConfig(String name, String owner){
		mName = name;
		mOwner = owner;
	}

	/**
	 * Called by the ControllerManager when registering the PluginController for this config 
	 * @param remote the access point to the remote class
	 */
	public void setRemoteController(PluginControllerInterface remote){
		mPluginControllerInterface = remote;
	}
	
	/**
	 * Listens for events related to the Visualization shown by this PluginController.
	 * Use this method to listen for button clicks etc.
	 * @param id the id of the SVG element to watch
	 */
	public void addVisualizationListener(String id) {
		mSubscribedSVGIds.add(id);
	}
	
	/**
	 * Use this function to supply a custom SVG Visualization in the form of an .svg file
	 * @param path the path to the custom .svg file
	 */
	public void setVisualizationPath(String path){
		mSvgUri = new File(path).toURI();
	}
	
	/**
	 * Use this function to supply a custom SVG Visualization in the form of a serialized string
	 * representing an SVG Document. See DocumentUtils.serializeDocument()
	 * @param data the string of serialized data
	 */
	public void setSerializedVisualization(String data) {
		mSerializedDocument = data;
	}
	
	/**
	 * @return the URI of any added svg Visualization file. Null if a serialized document was added instead.
	 */
	public URI getVisualizationURI() {
		return mSvgUri;
	}
	
	/**
	 * @return the serialized string representing an svg visualization. Null if no such string was added.
	 */
	public String getSerializedVisualization() {
		return mSerializedDocument;
	}
	
	/**
	 * @return an array of svg ids to which this PluginController wishes to subscribe for updates.
	 */
	public String[] getSubscribedIds() {
		return mSubscribedSVGIds.toArray(new String[0]);
	}

	/**
	 * Adds a subscription for events related to a specific device given by its name (i.e. Kitchen.temperature_1)
	 * @param name of the device to subscribe to events from
	 * @return a reference to this config to allow for chaining of calls
	 */
	public ControllerConfig addNameSubscription(String name){
		mNameSubscriptions.add(name);
		return this;
	}

	/**
	 * Adds a subscription for events related to a specific type of devices (i.e. DeviceType.Temperature)
	 * @param the type to subscribe to events from
	 * @return a reference to this config to allow for chaining of calls
	 */
	public ControllerConfig addTypeSubscription(DeviceType type){
		mTypeSubscriptions.add(type);
		return this;
	}

	/**
	 * Lets the controller take control of this devicetype
	 * @param devicetype the devicetype to take control of
	 * @return a reference to this config to allow for chaining of calls
	 */
	public ControllerConfig addControlledDeviceType(DeviceType type){
		mControlledTypes.add(type);
		return this;
	}

	/**
	 * Get the devicetypes controlled by this controller
	 * @return an enumset representing the devicetypes controlled by this controller
	 */
	public EnumSet<DeviceType> getControlledDeviceTypes(){
		return mControlledTypes;
	}

	/**
	 * Get the devicetypes subscribed to by this controller
	 * @return an enumset representing the devicetypes subscribed to by this controller
	 */
	public EnumSet<DeviceType> getSubscribedDeviceTypes(){
		return mTypeSubscriptions;
	}

	/**
	 * Get the ControllerParameters used by this controller
	 * @return a HashSet containing ControllerParameters
	 */
	public HashSet<ControllerParameter> getControllerParameters(){
		return mControllerParameters;
	}

	/**
	 * Called by the ControllerManager when an event is dispatched down the chain.
	 * This function will filter the event based on added subscriptions and pass it on to the
	 * controller itself
	 * @param s the sensor which triggered this update
	 */
	public void filter(Sensor s){
		boolean propagate = false;
		if (mTypeSubscriptions.contains(s.getType())){
			propagate = true;
		}
		else if (mNameSubscriptions.contains(s.getName())){
			propagate = true;
		}

		if (propagate){
			if (mPluginControllerInterface != null){
				try {
					mPluginControllerInterface.sensorUpdated(s);
				} catch (RemoteException e) {/*IGNORE*/}
			}
		}
	}

	/**
	 * Called by the ControllerManager when an event is dispatched down the chain.
	 * This function will filter the event based on added subscriptions and pass it on to the
	 * controller itself
	 * @param a the actuator which triggered this update
	 */
	public void filter(Actuator a){
		boolean propagate = false;
		if (mTypeSubscriptions.contains(a.getType())){
			propagate = true;
		}
		else if (mNameSubscriptions.contains(a.getName())){
			propagate = true;
		}

		if (propagate){
			if (mPluginControllerInterface != null){
				try {
					mPluginControllerInterface.actuatorUpdated(a);
				} catch (RemoteException e) {/*IGNORE*/}
			}
		}
	}

	public void filter(ControllerParameter param, CompositeString value){
		if (mControllerParameters.contains(param)){
			if (mPluginControllerInterface != null){
				try {
					mPluginControllerInterface.controllerParameterUpdated(param, value);
				} catch (RemoteException e) {/*IGNORE*/}
			}
		}
	}

	/**
	 * Get the name of this controller
	 * @return a string representing the name
	 */
	public String getName(){
		return mName;
	}
	
	/**
	 * Get the owner of this controller
	 * @return a string representing the name of the owner
	 */
	public String getOwner(){
		return mOwner;
	}

	/**
	 * Method used for heartbeat mechanism
	 */
	public PluginControllerInterface getControllerInterface(){
		return mPluginControllerInterface;
	}

	/**
	 * Called when a visualization has issued an event to the controller identified by this controllerconfig.
	 * @param event the event that triggered this call
	 */
	@Override
	public void onVisualizationEvent(VisualizationEvent event){
		try {
			mPluginControllerInterface.onVisualizationEvent(event);
		} catch (RemoteException e) {}
	}

	/**
	 * Sends the document to the plugincontroller by serializing it
	 * @param document the document to send over RMI
	 */
	@Override
	public void onDocumentReady(Document document){
		String output = DocumentUtils.serializeDocument(document);

		// Send serialized data
		if (output != null){
			try {
				mPluginControllerInterface.onDocumentReady(output.toString());
			} catch (RemoteException e) {}
		}
	}
}
